/**************************************************************
 * mp3id.c - id3v1 and id3v2.3 partially read write support
 * Copyright (c) 2009  Qin Xiaoxue  <crs.chin@gmail.com>
 *
 * This file is  free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
**************************************************************/


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#include <libgen.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>

#include "common.h"

#define IDV23_FRAME_ID
#include "metainfo_mp3.h"


/************************** ID3V1 ****************************/

const gchar *genre_list[] = {
  "Blues", "Classicrock", "Country", "Dance", "Disco", "Funk", "Grunge",
  "Hip Hop", "Jazz", "Metal", "Newage", "Oldies", "Other", "Pop", "R N B",
  "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska",
  "Deathmetal", "Preanks", "Sonndtrack", "Euro Techno", "Ambient", "Trip Hop",
  "Vocal", "Jazz Funk", "Fusion", "Trance", "Classical", "Instrumental",
  "Acide", "House", "Game", "Soundclip", "Gospel", "Noise", "Alternrock",
  "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumentalpop",
  "Instrumentalrock", "Ethnic", "Gothic", "Darkwave", "Techno Industrial",
  "Electronic", "Pop Folk", "Eurodace", "Dream", "Southerrock", "Comedy",
  "Cult", "Gansta", "Top40", "Christianrap", "Pop Funk", "Jungle", "Nativeamerica",
  "Cabaret", "Newwave", "Psychadelic", "Rave", "Showtunes", "Trailer",
  "Lo Fi", "Tribal", "Acidpunk", "Acidjazz", "Polka", "Retro", "Musical",
  "Rock", "Hardrock", "Folk", "Folk Rock", "Nativefolk", "Swing", "Fastfusion",
  "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothicrock",
  "Progressiverock", "Psichedelicrock", "Symphonicrock", "Slowrock",
  "Bigband", "Chorus", "Easylistening", "Acoustic", "Humour", "Speech",
  "Chanson", "Opera", "Chambermusic", "Sonata", "Symhony", "Bootybass", "Primus",
  "Porngroove", "Satire", "Slowjam", "Club", "Tango", "Samba", "Folklore",
  "Ballad", "Powerballad", "Rhythmicsoul", "Freestyle", "Duet", "Punkrock",
  "Drumsolo", "Acapella", "Euro House", "Dancehall", "Goa", "Drum", "Club House",
  "Hardcore", "Terror", "Indie", "Britpop", "Negerpunk", "Pilskpunk", "Beat",
  "Christiangangstarap", "Heavymetal", "Blackmetal", "Grossover",
  "Contemporarychristian", "Christianrock", "Merengue", "Salsa", "Trashmetal",
  "Anime", "Jpop", "Synthpop",
};


const gint genre_list_length = sizeof(genre_list) / sizeof(const gchar *);



ID3V1 *check_id3v1(FILE *fp)
{
  ID3V1 *id;

  if(! fp)
    return NULL;

  id = (ID3V1 *)checked_malloc(sizeof(ID3V1));
  fseek(fp, 0 - sizeof(ID3V1) , SEEK_END);
  if(secure_fread(id, 1, sizeof(ID3V1), fp) != sizeof(ID3V1)) {
    free(id);
    return NULL;
  }

  if(memcmp(id->header, "TAG", 3)) {
    free(id);
    return NULL;
  }

  return id;
}


int    write_id3v1(FILE *fp, ID3V1 *id)
{
  ID3V1 *oldid;

  if(! fp  ||  ! id)
    return FALSE;

  if(strncmp(id->header, "TAG", 3))  {
	print_verb("malformat id3v1 tag to write.\n");
	return FALSE;
  }

  oldid = check_id3v1(fp);
  if(oldid) {
    fseek(fp, 0 - sizeof(ID3V1) , SEEK_END);
    free(oldid);
  } else
    fseek(fp, 0, SEEK_END);

  if(secure_fwrite(id, 1, sizeof(ID3V1), fp)  == -1)
    return FALSE;

  return TRUE;
}


void   print_id3v1(ID3V1 *id)
{
  if(! id)
    return;

  printf("Title: %.30s",id->title ? id->title : "Unknown");
  printf("\tArtist: %.30s\n",id->artist ? id->artist : "Unknown");
  /*FIXME: genre omitted*/
  printf("Year:%.4s",id->year ? id->year : "Unknown");
  printf("\tAlbum: %.30s",id->album ? id->album : "Unknown");
  if(id->track)
    printf("\tTrack:%d\n",id->track);
  printf("Comment:\n\t%.28s\n",id->comment ? id->comment : "Unknown");
}


/************************** ID3V2.3 ***************************/

/*frame identification table*/

char *frame_table[]=  {
  /* NOTE: the following deffinition is sequence dependent,don't change it*/

  "AENC", "audio encryption",
  "APIC", "attached picture",

  "COMM", "comments",
  "COMR", "commercial frame",

  "ENCR", "encryption  method registration",
  "EQUA", "equalization",
  "ETCO", "event timing codes",

  "GEOB", "general encapsulated object",
  "GRID", "group identification registration",

  "IPLS", "involved people list",

  "LINK", "linked infomation",

  "MCDI", "music cd identifacation",
  "MLLT", "MPEG location lookup table",

  "OWNE", "ownership frame",

  "PRIV", "private frame",
  "PCNT", "play counter",
  "POPM", "popularimeter",
  "POSS", "postion synchronisation frame",

  "RBUF", "recommanded buffer size",
  "RVAD", "relative volumn adjustment",
  "RVRB", "reverb",

  "SYLT", "synchronized lyric/text",
  "SYTC", "synchronized tempo codes",

  "TALB", "album/movie/show title",
  "TBPM", "BPM (beats per minute)",
  "TCOM", "composer",
  "TCON", "content type",
  "TCOP", "copyight messege",
  "TDAT", "date",
  "TDLY", "playlist delay",
  "TEXT", "lyricist/text writer",
  "TFLT", "file type",
  "TIME", "time",
  "TIT1", "content group description",
  "TIT2", "title/ongname/content description",
  "TIT3", "subtitle/description refinement",
  "TKEY", "initial key",
  "TLAN", "languages",
  "TLEN", "length",
  "TMED", "media type",
  "TOAL", "original album/movie/show title",
  "TOFN", "original  file name",
  "TOLY", "original lyricist(s)/text writer(s)*/ ",
  "TOPE", "original artist(s)/performer(s)",
  "TORY", "original release year",
  "TOWN", "file owner/license",
  "TPE1", "lead performer(s)/soloist(t)",
  "TPE2", "band/orchestra/accompaniment",
  "TP3E", "conductor/performer refinement",
  "TPE4", "interpreted, remixed, or otherwise modifiedby",
  "TPOS", "part of a set",
  "TPUB", "publisher",
  "TRCK", "track number/position in set",
  "TRDA", "recording/dates",
  "TRSN", "internet radio station name",
  "TRSO", "internet radio station owner",
  "TSIZ", "size",
  "TSRC", "isrc (international statard recording code)",
  "TSSE", "software /hardware and settings used for encoding",
  "TYER", "year",
  "TXXX", "user defined text information frame",

  "UFID", "unique file identifier",
  "USER", "terms of use",
  "USLT", "unsychronized lyric/text transicription",

  "WCOM", "coercial infomation",
  "WCOP", "copyright/legal infomation",
  "WOAF", "offcial audio file webpage",
  "WOAR", "official artist/performer webpage",
  "WOAS", "official audio source webpage",
  "WORS", "officical internet radio station homepage",
  "WPAY", "payment",
  "WPUB", "publishers official webpage",
  "WXXX", "user defined URL link frame",

  NULL,NULL
};


static int id3v23_length(unsigned char *s)
{
  return (s[0]&0x7f)*0x200000 + (s[1]&0x7f)*0x400 +
    (s[2]&0x7f)*0x80 + (s[3]&0x7f);
/*   int i,j, ret = 0; */
/*   for(i = 3; i >= 0; i--) { */
/*     j = *(s+i); */
/*     j <<= 7*(3 - i); */
/*     ret |= j; */
/*   } */

/*   return ret; */
}


static int frame_length(unsigned char *s)
{
  return s[0]*0x1000000 + s[1]*0x10000 +
    s[2]*0x100 + s[3];
}


static char *encode_id3v23_length(int i, char *buf)
{
  int j = 4;
  for(; j--;)  buf[j] = 0;
  j = 3;
  while(i) {
    buf[j] = i%0x80;
    i /= 0x80;
    if(! j && i)  return NULL;
    i--;
  }
  return buf;
    
}


static char *encode_frame_length(int i, char *buf)
{
  int j = 4;
  for(; j--;)  buf[j] = 0;
  j = 3;
  while(i) {
    buf[j] = i%0x100;
    i /= 0x100;
    if(! j && i)  return NULL;
    i--;
  }
  return buf;
}

void dump_id3v23_info(ID3V23H *id)
{
  fprintf(stderr, "******* dump ID3V2.3 header info ********\n"
	  "** RAW SIZE:%#04x %02x %02x %02x\t SIZE:%d\n", id->size[0], id->size[1],
	  id->size[2], id->size[3], id3v23_length(id->size));
}

void dump_frame_info(FRAMEH *frmh)
{
  fprintf(stderr, "******** dump ID3V2.3 frame info *********\n"
	  "** TAG:%.4s\n** RAW SIZE:%#04x %02x %02x %02x\n** SIZE:%d.\n",
	  frmh->frameid, frmh->size[0], frmh->size[1], frmh->size[2], 
	  frmh->size[3], frame_length(frmh->size));
}


int has_id3v23(FILE *fp)
{
  ID3V23H idh;

  if(! fp)
    return FALSE;

  rewind(fp);
  if(secure_fread(&idh, 1, sizeof(ID3V23H), fp) < sizeof(ID3V23H))
    return FALSE;

  if(strncmp(idh.header, "ID3", 3))
    return FALSE;

  return TRUE;
}


ID3V23 *check_id3v23(FILE *fp)
{
  ID3V23 *id;
  FRAME *frame, **tail;
  int id3size, framesize;

  if(! has_id3v23(fp))
    return NULL;

  id = (ID3V23 *)checked_malloc(sizeof(ID3V23));
  rewind(fp);
  if(fread(& id->header, 1, sizeof(ID3V23H), fp) < sizeof(ID3V23H)) {
    free(id);
    return NULL;
  }

  /* FIXME:
   * this lib refused to process if any flag id ID3V23H.flag is
   * set for id3v2.3 extensions, mostly the flag is NULL. also
   * this lib only process id3 versions of which  marchs ID3V2.3
   * exactly.
   */
  if(id->header.flag || 
     id->header.version != 3  ||
     id->header.revision != 0)   {
    free(id);
#ifndef NDEBUG
    fprintf(stderr, "** ID3V2.3 version expected or some extended feature detected.\n");
#endif
    return NULL;
  }
  id->frame = NULL;
  tail = &id->frame;

  id3size = id3v23_length(id->header.size);
  id3size -= sizeof(ID3V23H);
  //  id->len = id3size;

  while(id3size) {
    frame = (FRAME *)checked_malloc(sizeof(FRAME));
    frame->next = NULL;
    if(secure_fread(&frame->frameh, 1, sizeof(FRAMEH), fp) < sizeof(FRAMEH)) {
#ifndef NDEBUG
      fprintf(stderr, "** frame header read error at offset %d\n", ftell(fp));
#endif
      //      goto bad_or_corupted_id3v2;
      free(frame);
      break;
    }

    if(memchr(frame->frameh.frameid, '\0', 4)) {
#ifndef NDEBUG
      fprintf(stderr, "** padding encounter maybe");
#endif
      free(frame);
      break;
    }

    framesize = frame_length(frame->frameh.size);
    if(framesize < 0) {
#ifndef NDEBUG
      fprintf(stderr, "** frame header size corrupted.\n");
#endif
      //      goto bad_or_corupted_id3v2;
      free(frame);
      break;
    }

    frame->data = (char *)checked_malloc(framesize);
    frame->data_len = framesize;
    if(secure_fread(frame->data, 1, framesize, fp)  < framesize) {
#ifndef NDEBUG
      fprintf(stderr, "** frame data read error at offset %d.\n", ftell(fp));
      dump_frame_info(&frame->frameh);
#endif
      //      goto bad_or_corupted_id3v2;
      free(frame->data);
      free(frame);
      break;
    }

    id->len += sizeof(FRAMEH) + framesize;

    *tail = frame;
    tail = &frame->next;

    id3size -= sizeof(FRAMEH) + framesize;

	/* FIXME : something must be wrong about it. */
    if(id3size < 0 || id->len > 1024*1024*5) {
#ifndef NDEBUG
      fprintf(stderr, "** ID3V23 size don't march with content size.\n");
      dump_id3v23_info(& id->header);
#endif
      //      goto bad_or_corupted_id3v2;
      break;
    }
  }


  return id;

 bad_or_corupted_id3v2:
  //  print_id3v23_info(id, 1);
  free(frame);
  free_id3v23(id);
  return NULL;
}



static  FRAME *find_id3v23_frame(ID3V23 *id3, int frameid)
{
  FRAME *ret;

  if(! id3  ||  frameid >= TEND || frameid < 1)
    return NULL;

  ret = id3->frame;
  while(ret) {
    if(! strncmp(frame_table[(frameid - 1) * 2], ret->frameh.frameid, 4))
      break;
    ret = ret->next;
  }

  return ret;
}

int     write_id3v23_frame(ID3V23 *id3, int frameid, char *data, int len)
{
  FRAME *frm, *tmp;
  int ischange = 1, id3len;

  if(! id3 || frameid >= TEND || frameid < 1)
    return FALSE;

  frm = find_id3v23_frame(id3, frameid);
  if(! frm) {
    ischange = 0;
    id3len = id3->len + sizeof(FRAME) + len;
    frm = (FRAME *)checked_malloc(sizeof(FRAME));
    bzero(frm, sizeof(FRAME));
  }else
    id3len = id3->len + len - frm->data_len;

  frm->data = (char *)checked_realloc(frm->data, len);
  memcpy(frm->data, data, len);
  frm->data_len = len;
  encode_frame_length(len, frm->frameh.size);

  if(! ischange) {
    tmp = id3->frame;
    while(tmp->next)
      tmp = tmp->next;

    memcpy(frm->frameh.frameid, frame_table[(frameid - 1) * 2], 4);

    tmp->next = frm;
  }
  id3->len = id3len;

  return TRUE;
}



int     read_id3v23_frame(ID3V23 *id3, int frameid, char **data, int *len)
{
  FRAME *frm;

  frm = find_id3v23_frame(id3, frameid);
  if(! frm) {
    *data = NULL;
    return FALSE;
  }

  *len = frm->data_len + 1;
  *data = (char *)checked_malloc(frm->data_len);
  memcpy(*data, frm->data, *len);
  *(*data + *len) = '\0';

  return TRUE;
}


/*this will overwrite preexist ID3V2.3 info*/
int     id3v23_attach(FILE *fp, ID3V23 *id3)
{
  char buf[1024];
  FILE *tmpfp = tmpfile();
  int tmp, skip = 0;
  ID3V23H idh;
  FRAME *frm;

  if(!tmpfp || !fp || !id3)
    return FALSE;

  rewind(fp);
  if(secure_fread(&idh, 1, sizeof(ID3V23H), fp) == sizeof(ID3V23H))
    if(! strncmp(idh.header, "ID3", 3))
      skip = id3v23_length(idh.size);

  fseek(fp, skip, SEEK_SET);
  do{
    tmp = secure_fread(buf, 1, 1024, fp);
    secure_fwrite(buf, 1, tmp, tmpfp);
  }while(! feof(fp));

  rewind(tmpfp);
  rewind(fp);
  if(ftruncate(fileno(fp), 0) == -1) {
    perror("** truncate file");
    return FALSE;
  }

  frm = id3->frame;
  encode_id3v23_length(id3->len, id3->header.size);
  secure_fwrite(& id3->header, 1, sizeof(ID3V23H), fp);
  while(frm) {
    secure_fwrite(& frm->frameh, 1, sizeof(FRAMEH), fp);
    secure_fwrite(frm->data, 1, frm->data_len, fp);
    frm = frm->next;
  }

  do{
     tmp = secure_fread(buf, 1, 1024, tmpfp);
     secure_fwrite(buf, 1, tmp, fp);
  }while(! feof(tmpfp));

  return TRUE;
}


void    free_id3v23(ID3V23 *id3)
{
  FRAME *tmp, *tmp1;

  if(! id3)
    return;

  tmp = id3->frame;
  while(tmp) {
    if(tmp->data)
      free(tmp->data);
    tmp1 = tmp;
    tmp = tmp->next;
    free(tmp1);
  }
  free(id3);
}


void    print_id3v23_info(ID3V23 *id3, int all)
{
  FRAME *frm;

  if(! id3)  return;

#ifndef NDEBUG
  printf("(DD): ID3 length:%d\n", id3->len);
#endif

  if(all) {
    frm = id3->frame;
    while(frm) {
#ifndef NDEBUG
      printf("(DD):len:%d\n", frm->data_len);
#endif
      printf("%.*s:%.*s\n\n", 4, frm->frameh.frameid,
	     frm->data[0] ? frm->data_len : frm->data_len - 1,
	     frm->data[0] ? frm->data : &frm->data[1]);
      frm = frm->next;
    }
  }else {
#define __print_frame_info(id3, frame, frameid, info)                      \
    frame = find_id3v23_frame(id3, frameid);                               \
    __debug_frame_info(frame);                                             \
    if(frame)                                                              \
      printf(info, frame->data[0] ? frame->data_len : frame->data_len - 1, \
	     frame->data[0] ? frame->data : &frame->data[1])


#ifndef NDEBUG
#define __debug_frame_info(frame)                 \
    if(! frame)                                   \
      printf("(DD):frame is NULL\n");             \
    else                                          \
      printf("(DD):frame len: %d\n", frame->data_len)
#else
#define __debug_frame_info(frame)   do{}while(0)
#endif


    __print_frame_info(id3, frm, TIT2 ,"TITLE: %.*s\t\t");
    __print_frame_info(id3, frm, TPE1 ,"ARTIST: %.*s\n");
    __print_frame_info(id3, frm, TALB ,"ALBUM: %.*s\t\t");
    __print_frame_info(id3, frm, TCON ,"GENRE: %.*s\n");
    __print_frame_info(id3, frm, TYER ,"YEAR: %.*s\t\t");
    __print_frame_info(id3, frm, TRCK ,"TRACK: %.*s\n");
    __print_frame_info(id3, frm, COMM ,"COMMENT: %.*s\n");

#undef __print_frame_info
  }
}



/* glue code from mp3id.c to metainfo.c */

struct mp3_private{
  ID3V1 *idv1;
  ID3V23 *idv2;
};

gboolean mp3_probe(FILE *fp)
{
/*   ID3V1 *id1; */

/*   id1 = check_id3v1(fp); */
/*   if(! id1)  { */
/* 	if(has_id3v23(fp)) */
/* 	  return TRUE; */
/* 	return FALSE; */
/*   } */

/*   free(id1); */
  return FALSE;
}

gboolean mp3_read(FILE *fp, struct meta *meta)
{
  struct mp3_private *ids;
  gint len;

  ids = g_new0(struct mp3_private, 1);
  ids->idv1 = check_id3v1(fp);
  ids->idv2 = check_id3v23(fp);
  if(! (ids->idv1 || ids->idv2))  {
	g_free(ids);
	return FALSE;
  }

  if(ids->idv1)  {
	if(ids->idv1->artist[0])
	  meta->artist = g_strdup(ids->idv1->artist);
	if(ids->idv1->title[0])
	  meta->title = g_strdup(ids->idv1->title);
	if(ids->idv1->album[0])
	  meta->album = g_strdup(ids->idv1->album);
	if(ids->idv1->year[0])
	  meta->year = g_strdup(ids->idv1->year);
	if(ids->idv1->comment[0])
	  meta->comment = g_strdup(ids->idv1->comment);
	meta->track = g_strdup_printf("%c", ids->idv1->track);
	if(ids->idv1->genre >= 0 && ids->idv1->genre < genre_list_length)
	  meta->genre = g_strdup(genre_list[ids->idv1->genre]);
  }

  if(ids->idv2)  {
	if(! meta->artist)
	  read_id3v23_frame(ids->idv2, TIT2, &meta->artist, &len);
	if(! meta->title)
	  read_id3v23_frame(ids->idv2, TPE1, &meta->title, &len);
	if(! meta->album)
	  read_id3v23_frame(ids->idv2, TALB, &meta->album, &len);
	if(! meta->year)
	  read_id3v23_frame(ids->idv2, TYER, &meta->year, &len);
	if(! meta->comment)
	  read_id3v23_frame(ids->idv2, COMM, &meta->comment, &len);
  }

  meta->data = ids;
  return TRUE;
}


/* gboolean mp3_write(FILE *fp, struct meta *meta) */
/* { */
/*   struct mp3_private *ids = (struct mp3_private *)(meta->data); */

/*   /\* FIXME: not implemented *\/ */
/*   return TRUE; */
/* } */


gboolean mp3_release(struct meta *meta)
{
  struct mp3_private *ids = (struct mp3_private *)(meta->data);

  g_free(ids->idv1);
  free_id3v23(ids->idv2);
  g_free((void *)meta->internal_encoding);
  g_free((void *)meta->lang);
  g_free(meta->data);

  return TRUE;
}





/*********************************************************
                     TESTING CODE
*********************************************************/
#ifdef TEST_MP3ID

int main(int argc, char **argv)
{
  FILE *fp;
  ID3V23 *id3;

  if(argc > 1)
    fp = fopen(argv[1],"r");
  else
    fp = fopen("1.mp3","r");

  if(! fp) {
    puts("cannt open file");
    return 1;
  }

/*   print_id3v1(check_id3v1(fp)); */

/*   char *artist; */
/*   printf(unescape("abcd98%20e_%20f%2eg%34hi\n")); */
/*   printf("title:%s\n", */
/* 	 guess_filename("abcd98%20e_%20f%2eg%34hi\n",&artist)); */
/*   printf("artist:%s\n",artist ? artist : "(NULL)"); */

  puts("=================================================");

  id3 = check_id3v23(fp);
  if(! id3) {
    printf("no id3v2 tag found\n");
    return 0;
  }
  print_id3v23_info(id3, 1);
  puts("%%%%\n");
  print_id3v23_info(id3, 0);

  puts("++++++++++++++++++++++++++++++++++++++++++++++++");
  char *data;
  int len;
  if(read_id3v23_frame(id3, TIT2, &data, &len))
    printf("%.*s\n", len-1, data+1);
  else
    printf("** not found.\n");




  free_id3v23(id3);


  fclose(fp);

  return 0;
}
#endif
